home *** CD-ROM | disk | FTP | other *** search
/ ADA Programming Guide / ADA Programming Guide.iso / ada_gnu / adainc / a-calend.adb < prev    next >
Text File  |  1996-01-30  |  16KB  |  453 lines

  1. ------------------------------------------------------------------------------
  2. --                                                                          --
  3. --                         GNAT RUNTIME COMPONENTS                          --
  4. --                                                                          --
  5. --                         A D A . C A L E N D A R                          --
  6. --                                                                          --
  7. --                                 B o d y                                  --
  8. --                                                                          --
  9. --                            $Revision: 1.23 $                             --
  10. --                                                                          --
  11. --           Copyright (c) 1992,1993,1994 NYU, All Rights Reserved          --
  12. --                                                                          --
  13. -- The GNAT library is free software; you can redistribute it and/or modify --
  14. -- it under terms of the GNU Library General Public License as published by --
  15. -- the Free Software  Foundation; either version 2, or (at your option) any --
  16. -- later version.  The GNAT library is distributed in the hope that it will --
  17. -- be useful, but WITHOUT ANY WARRANTY;  without even  the implied warranty --
  18. -- of MERCHANTABILITY  or  FITNESS FOR  A PARTICULAR PURPOSE.  See the  GNU --
  19. -- Library  General  Public  License for  more  details.  You  should  have --
  20. -- received  a copy of the GNU  Library  General Public License  along with --
  21. -- the GNAT library;  see the file  COPYING.LIB.  If not, write to the Free --
  22. -- Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.        --
  23. --                                                                          --
  24. ------------------------------------------------------------------------------
  25.  
  26. with System; use System;
  27. with System.Task_Clock;
  28. with System.Task_Clock.Machine_Specifics;
  29.  
  30. package body Ada.Calendar is
  31.  
  32.    ------------------------------
  33.    -- Use of Pragma Unsuppress --
  34.    ------------------------------
  35.  
  36.    --  This implementation of Calendar takes advantage of the permission in
  37.    --  Ada 9X of using arithmetic overflow checks to check for out of bounds
  38.    --  time values. This means that we must catch the constraint error that
  39.    --  results from arithmetic overflow, so we use pragma Unsuppress to make
  40.    --  sure that overflow is enabled, using software overflow checking if
  41.    --  necessary. That way, compiling Calendar with options to suppress this
  42.    --  checking will not affect its correctness.
  43.  
  44.    ------------------------
  45.    -- Local Declarations --
  46.    ------------------------
  47.  
  48.    type Char_Pointer is access Character;
  49.  
  50.    type tm is record
  51.       tm_sec    : Integer range 0 .. 60;  -- seconds after the minute
  52.       tm_min    : Integer range 0 .. 59;  -- minutes after the hour
  53.       tm_hour   : Integer range 0 .. 23;  -- hours since midnight
  54.       tm_mday   : Integer range 1 .. 31;  -- day of the month
  55.       tm_mon    : Integer range 0 .. 11;  -- months since January
  56.       tm_year   : Integer;                -- years since 1900
  57.       tm_wday   : Integer range 0 .. 6;   -- days since Sunday
  58.       tm_yday   : Integer range 0 .. 365; -- days since January 1
  59.       tm_isdst  : Integer range -1 .. 1;  -- Daylight Savings Time flag
  60.       tm_gmtoff : Long_Integer;           -- offset from CUT in seconds
  61.       tm_zone   : Char_Pointer;           -- timezone abbreviation
  62.    end record;
  63.  
  64.    type tm_Pointer is access tm;
  65.  
  66.    subtype time_t is Long_Integer;
  67.  
  68.    type time_t_Pointer is access time_t;
  69.  
  70.    function localtime (C : time_t_Pointer) return tm_Pointer;
  71.    pragma Import (C, localtime);
  72.  
  73.    function mktime (TM : tm_Pointer) return time_t;
  74.    pragma Import (C, mktime);
  75.    --  mktime returns -1 in case the calendar time given by components of
  76.    --  TM.all cannot be represented.
  77.  
  78.    --  The following constants are used in adjusting Ada dates so that they
  79.    --  fit into the range that can be handled by Unix (1970 - 2038). The trick
  80.    --  is that the number of days in any four year period in the Ada range of
  81.    --  years (1901 - 2099) has a constant number of days. This is because we
  82.    --  have the special case of 2000 which, contrary to the normal exception
  83.    --  for centuries, is a leap year after all.
  84.  
  85.    Unix_Year_Min       : constant := 1970;
  86.    Unix_Year_Max       : constant := 2038;
  87.  
  88.    --  These values is to find the maximum Duration vaules
  89.    --  For Time used in Split. (MaxD and MinD)
  90.  
  91.    Unix_Year_Min_In_Duration : constant Duration :=
  92.      Duration (Time_Of (Unix_Year_Min, 1, 1, 0.0));
  93.    Unix_Year_Max_In_Duration : constant Duration :=
  94.      Duration (Time_Of (Unix_Year_Max, 1, 1, 0.0));
  95.  
  96.    Ada_Year_Min        : constant := 1901;
  97.    Ada_Year_Max        : constant := 2099;
  98.  
  99.    Days_In_Month       : constant array (Month_Number) of Day_Number :=
  100.     (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  101.  
  102.    Days_In_4_Years     : constant := 365 * 3 + 366;
  103.    Seconds_In_4_Years  : constant := 86_400 * Days_In_4_Years;
  104.    Seconds_In_4_YearsD : constant Duration := Duration (Seconds_In_4_Years);
  105.  
  106.    ---------
  107.    -- "+" --
  108.    ---------
  109.  
  110.    function "+" (Left : Time; Right : Duration) return Time is
  111.       pragma Unsuppress (Overflow_Check);
  112.    begin
  113.       return (Left + Time (Right));
  114.    exception
  115.       when Constraint_Error => raise Time_Error;
  116.    end "+";
  117.  
  118.    function "+" (Left : Duration; Right : Time) return Time is
  119.       pragma Unsuppress (Overflow_Check);
  120.    begin
  121.       return (Time (Left) + Right);
  122.    exception
  123.       when Constraint_Error => raise Time_Error;
  124.    end "+";
  125.  
  126.    ---------
  127.    -- "-" --
  128.    ---------
  129.  
  130.    function "-" (Left : Time; Right : Duration)  return Time is
  131.       pragma Unsuppress (Overflow_Check);
  132.    begin
  133.       return Left - Time (Right);
  134.    exception
  135.       when Constraint_Error => raise Time_Error;
  136.    end "-";
  137.  
  138.    function "-" (Left : Time; Right : Time) return Duration is
  139.       pragma Unsuppress (Overflow_Check);
  140.    begin
  141.       return Duration (Left) - Duration (Right);
  142.    exception
  143.       when Constraint_Error => raise Time_Error;
  144.    end "-";
  145.  
  146.    ---------
  147.    -- "<" --
  148.    ---------
  149.  
  150.    function "<" (Left, Right : Time) return Boolean is
  151.    begin
  152.       return Duration (Left) < Duration (Right);
  153.    end "<";
  154.  
  155.    ----------
  156.    -- "<=" --
  157.    ----------
  158.  
  159.    function "<=" (Left, Right : Time) return Boolean is
  160.    begin
  161.       return Duration (Left) <= Duration (Right);
  162.    end "<=";
  163.  
  164.    ---------
  165.    -- ">" --
  166.    ---------
  167.  
  168.    function ">" (Left, Right : Time) return Boolean is
  169.    begin
  170.       return Duration (Left) > Duration (Right);
  171.    end ">";
  172.  
  173.    ----------
  174.    -- ">=" --
  175.    ----------
  176.  
  177.    function ">=" (Left, Right : Time) return Boolean is
  178.    begin
  179.       return Duration (Left) >= Duration (Right);
  180.    end ">=";
  181.  
  182.    -----------
  183.    -- Clock --
  184.    -----------
  185.  
  186.    --  The Ada.Calendar.Clock function gets the time from the GNULLI
  187.    --  interface routines. This ensures that Calendar is properly
  188.    --  coordinated with the tasking runtime. Any system dependence
  189.    --  involved in reading the clock is then hidden in the GNULLI
  190.    --  implementation layer (in the body of System.Task_Clock).
  191.  
  192.    function Clock return Time is
  193.    begin
  194.       return Time (Task_Clock.Stimespec_To_Duration (
  195.             Task_Clock.Machine_Specifics.Clock));
  196.    end Clock;
  197.  
  198.    ---------
  199.    -- Day --
  200.    ---------
  201.  
  202.    function Day (Date : Time) return Day_Number is
  203.       DY : Year_Number;
  204.       DM : Month_Number;
  205.       DD : Day_Number;
  206.       DS : Day_Duration;
  207.  
  208.    begin
  209.       Split (Date, DY, DM, DD, DS);
  210.       return DD;
  211.    end Day;
  212.  
  213.    -----------
  214.    -- Month --
  215.    -----------
  216.  
  217.    function Month (Date : Time) return Month_Number is
  218.       DY : Year_Number;
  219.       DM : Month_Number;
  220.       DD : Day_Number;
  221.       DS : Day_Duration;
  222.  
  223.    begin
  224.       Split (Date, DY, DM, DD, DS);
  225.       return DM;
  226.    end Month;
  227.  
  228.    -------------
  229.    -- Seconds --
  230.    -------------
  231.  
  232.    function Seconds (Date : Time) return Day_Duration is
  233.       DY : Year_Number;
  234.       DM : Month_Number;
  235.       DD : Day_Number;
  236.       DS : Day_Duration;
  237.  
  238.    begin
  239.       Split (Date, DY, DM, DD, DS);
  240.       return DS;
  241.    end Seconds;
  242.  
  243.    -----------
  244.    -- Split --
  245.    -----------
  246.  
  247.    procedure Split
  248.      (Date    : Time;
  249.       Year    : out Year_Number;
  250.       Month   : out Month_Number;
  251.       Day     : out Day_Number;
  252.       Seconds : out Day_Duration)
  253.    is
  254.       pragma Unsuppress (Overflow_Check);
  255.  
  256.       --  The following declare bounds for duration that are comfortably
  257.       --  wider than the maximum allowed output result for the Ada range
  258.       --  of representable split values. These are used for a quick check
  259.       --  that the value is not wildly out of range.
  260.  
  261.       Low  : constant := (Ada_Year_Min - Unix_Year_Min - 2) * 365 * 86_400;
  262.       High : constant := (Ada_Year_Max - Unix_Year_Max + 2) * 365 * 86_400;
  263.  
  264.       LowD  : constant Duration :=
  265.         Duration (Low) + Unix_Year_Min_In_Duration;
  266.       HighD : constant Duration :=
  267.         Duration (High) + Unix_Year_Max_In_Duration;
  268.  
  269.       --  The following declare the maximum duration value that can be
  270.       --  successfully converted to a 32-bit integer suitable for passing
  271.       --  to the localtime function. It might be more correct to use the
  272.       --  value Integer'Last here, but it is actually more conservative
  273.       --  to use the given value, since we are not really sure that the
  274.       --  range of allowable times expands on 64-bit machines!
  275.  
  276.       Max_Time  : constant := 2 ** 31 - 1;
  277.       Max_TimeD : constant Duration := Duration (Max_Time);
  278.  
  279.       --  Finally the actual variables used in the computation
  280.  
  281.       D                : Duration := Duration (Date);
  282.       Years_Adjust     : Integer  := 0;
  283.       Adjusted_Seconds : aliased time_t;
  284.       Tm_Val           : tm_Pointer;
  285.  
  286.    begin
  287.       --  First of all, filter out completely ludicrous values. Remember
  288.       --  that we use the full stored range of duration values, which may
  289.       --  be significantly larger than the allowed range of Ada times. Note
  290.       --  that these checks are wider than required to make absolutely sure
  291.       --  that there are no end effects from time zone differences.
  292.  
  293.       if D < LowD or else D > HighD then
  294.          raise Time_Error;
  295.       end if;
  296.  
  297.       --  The unix localtime function is more or less exactly what we need
  298.       --  here. The less comes from the fact that it does not support the
  299.       --  required range of years (the guaranteed range available is only
  300.       --  EPOCH through EPOCH + N seconds). N is in practice 2 ** 31 - 1.
  301.  
  302.       --  If we have a value outside this range, then we first adjust it
  303.       --  to be in the required range by adding multiples of four years.
  304.       --  For the range we are interested in, the number of days in any
  305.       --  consecutive four year period is constant. Then we do the split
  306.       --  on the adjusted value, and readjust the years value accordingly.
  307.  
  308.       while D < 0.0 loop
  309.          D := D + Seconds_In_4_YearsD;
  310.          Years_Adjust := Years_Adjust - 4;
  311.       end loop;
  312.  
  313.       while D > Max_TimeD loop
  314.          D := D - Seconds_In_4_YearsD;
  315.          Years_Adjust := Years_Adjust + 4;
  316.       end loop;
  317.  
  318.       Adjusted_Seconds := time_t (D);
  319.       Tm_Val := localtime (Adjusted_Seconds'Access);
  320.  
  321.       Year   := Tm_Val.tm_year + 1900 + Years_Adjust;
  322.       Month  := Tm_Val.tm_mon + 1;
  323.       Day    := Tm_Val.tm_mday;
  324.  
  325.       --  The Seconds value is a little complex. The localtime function
  326.       --  returns the integral number of seconds, which is what we want,
  327.       --  but we want to retain the fractional part from the original
  328.       --  Time value, since this is typically stored more accurately.
  329.  
  330.       Seconds := Duration (Tm_Val.tm_hour * 3600 +
  331.                            Tm_Val.tm_min  * 60 +
  332.                            Tm_Val.tm_sec)
  333.                    + (D - Duration (Long_Long_Integer (D)));
  334.  
  335.    --  The exception handler catches the case of a result Year out of range.
  336.    --  This can happen despite the entry test which was deliberately crude.
  337.    --  Trying to make it accurate is impossible because of time zone adjust
  338.    --  issues affecting the exact boundary (it is an interesting fact that
  339.    --  whether or not a given time value gets Time_Error when split depends
  340.    --  on the current time zone).
  341.  
  342.    exception
  343.       when Constraint_Error => raise Time_Error;
  344.  
  345.    end Split;
  346.  
  347.    -------------
  348.    -- Time_Of --
  349.    -------------
  350.  
  351.    function Time_Of
  352.      (Year    : Year_Number;
  353.       Month   : Month_Number;
  354.       Day     : Day_Number;
  355.       Seconds : Day_Duration := 0.0)
  356.       return    Time
  357.    is
  358.       Result_Secs : aliased time_t;
  359.       TM_Val      : aliased tm;
  360.       Int_Secs    : constant Integer := Integer (Seconds - 0.5);
  361.  
  362.       Year_Val        : Integer := Year;
  363.       Duration_Adjust : Duration := 0.0;
  364.  
  365.    begin
  366.       --  The following checks are redundant with respect to the constraint
  367.       --  error checks that should normally be made on parameters, but we
  368.       --  decide to raise Constraint_Error in any case if bad values come
  369.       --  in (as a result of checks being off in the caller, or for other
  370.       --  erroneous or bounded error cases). Note that eventually, when we
  371.       --  implement the attribute 'Valid, it should be used here instead ???
  372.  
  373.       if Integer (Year) not in Year_Number
  374.         or else Integer (Month) not in Month_Number
  375.         or else Integer (Day) < 1
  376.         or else Seconds < 0.0
  377.         or else Seconds > 86_400.0
  378.       then
  379.          raise Constraint_Error;
  380.       end if;
  381.  
  382.       --  Check for Day value too large
  383.  
  384.       if (Year mod 4 = 0) and then Month = 2 then
  385.          if Day > 29 then
  386.             raise Time_Error;
  387.          end if;
  388.       elsif Day > Days_In_Month (Month) then
  389.          raise Time_Error;
  390.       end if;
  391.  
  392.       --  Note: the mktime function supposedly does some error checking, but
  393.       --  at least on some systems it isn't strong enough, which is why we
  394.       --  do our own checking in the code above.
  395.  
  396.       TM_Val.tm_sec  := Int_Secs mod 60;
  397.       TM_Val.tm_min  := (Int_Secs / 60) mod 60;
  398.       TM_Val.tm_hour := (Int_Secs / 60) / 60;
  399.       TM_Val.tm_mday := Day;
  400.       TM_Val.tm_mon  := Month - 1;
  401.  
  402.       --  For the year, we have to adjust it to a year that Unix can handle.
  403.       --  We do this in four year steps, since the number of days in four
  404.       --  years is constant, so the timezone effect on the conversion from
  405.       --  local time to GMT is unaffected.
  406.  
  407.       while Year_Val <= Unix_Year_Min loop
  408.          Year_Val := Year_Val + 4;
  409.          Duration_Adjust := Duration_Adjust - Seconds_In_4_YearsD;
  410.       end loop;
  411.  
  412.       while Year_Val >= Unix_Year_Max loop
  413.          Year_Val := Year_Val - 4;
  414.          Duration_Adjust := Duration_Adjust + Seconds_In_4_YearsD;
  415.       end loop;
  416.  
  417.       TM_Val.tm_year := Year_Val - 1900;
  418.  
  419.       --  Since we do not have information on daylight savings,
  420.       --  rely on the default information.
  421.       TM_Val.tm_isdst := -1;
  422.  
  423.       Result_Secs := mktime (TM_Val'Access);
  424.  
  425.       --  That gives us the basic value in seconds. Two adjustments are
  426.       --  needed. First we must undo the year adjustment carried out above.
  427.       --  Second we put back the fraction seconds value since in general the
  428.       --  Day_Duration value we received has additional precision which we
  429.       --  do not want to lose in the constructed result.
  430.  
  431.       return
  432.         Time (Duration (Result_Secs) +
  433.               Duration_Adjust +
  434.               (Seconds - Duration (Int_Secs)));
  435.    end Time_Of;
  436.  
  437.    ----------
  438.    -- Year --
  439.    ----------
  440.  
  441.    function Year (Date : Time) return Year_Number is
  442.       DY : Year_Number;
  443.       DM : Month_Number;
  444.       DD : Day_Number;
  445.       DS : Day_Duration;
  446.  
  447.    begin
  448.       Split (Date, DY, DM, DD, DS);
  449.       return DY;
  450.    end Year;
  451.  
  452. end Ada.Calendar;
  453.